package game

import (
	"time"
	"gitee.com/lwow2025/snake-go/common/util"
)

const(
	frame = 60 * time.Millisecond
)

type SnakeAction struct {
	snakeID string
	direction Direction
}

func NewSnakeAction(sID string, d Direction) SnakeAction {
	return SnakeAction{ snakeID: sID, direction: d }
}

type AbsSnake interface {
	ID() string
	Move(foods []*Food) (ateFoods []*Food)
	Frame() []*Point
	ChangeDirection(direction Direction)
}

type winSizer interface {
	Width() int
	Height() int
}

type printer interface {
	Output([]*Point)
}

func NewGame(p printer, snakes []AbsSnake, sizer winSizer) *Game {
	mappedSnakes := map[string]AbsSnake{}
	for _, s := range snakes {
		id := s.ID()
		if id == "" {
			panic("snake id can't be empty")
		}
		if mappedSnakes[id] != nil {
			panic("duplicate snake:" + id)
		}
		mappedSnakes[id] = s
	}

	return &Game{
		foods: []*Food{ genFood(sizer.Width(), sizer.Height()) },
		snakes: mappedSnakes,
		printer: p,
		sizer: sizer,

		snakeActionChan: make(chan SnakeAction),
	}
}

type Game struct {
	foods []*Food
	snakes     map[string]AbsSnake
	printer printer
	sizer winSizer

	snakeActionChan chan SnakeAction
	// 初始化不要赋值，在start处赋值
	stopChan chan struct{}
}

func (g *Game) Start() error {
	g.stopChan = make(chan  struct{})
	go g.loop()
	return nil
}

func (g *Game) Stop() {
	if util.StopChanClosed(g.stopChan) {
		return
	}
	close(g.stopChan)
}

func (g *Game) DoSnakeAction(action SnakeAction) {
	g.snakeActionChan <- action
}

func (g *Game) loop() {
	tick := time.Tick(frame)
	out:
	for {
		select {
		case action := <- g.snakeActionChan:
			if snake := g.snakes[action.snakeID]; snake != nil {
				snake.ChangeDirection(action.direction)
			}
		case <- tick:
			// do actions（可以考虑只输出变化，变化通过序列化编号来保证正确性）
			g.moveSnakes()
			g.outputFrame()
		case <- g.stopChan:
			break out
		}
	}
}

func (g *Game) moveSnakes() {
	originFoodsLen := len(g.foods)
	//log.L.Debug("move snakes", zap.Int("snake count", len(g.snakes)))
	for _, s := range g.snakes {
		g.foods = s.Move(g.foods)
	}
	if len(g.foods) < originFoodsLen {
		g.foods = append(g.foods, genFood(g.sizer.Width(), g.sizer.Height()))
	}
}

func (g *Game) outputFrame() {
	var aFrame []*Point
	for _, s := range g.snakes {
		aFrame = append(aFrame, s.Frame()...)
	}
	for _, f := range g.foods {
		aFrame = append(aFrame, f.Position)
	}
	g.printer.Output(aFrame)
}